package cn.haigui.icrawler.core;

import cn.haigui.icrawler.core.downloader.DefaultDownloaderFactory;
import cn.haigui.icrawler.core.downloader.IDownloader;
import cn.haigui.icrawler.core.downloader.IDownloaderFactory;
import cn.haigui.icrawler.core.parser.Parser;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

@Accessors(fluent = true)
@Slf4j
public class Task {

    public static String DEFAULT_NAME = "haigui";
    private static Logger taskLogger = LoggerFactory.getLogger("task");


    @Getter
    @Setter
    @Builder.Default
    private String name = DEFAULT_NAME;

    @Getter
    private List<String> seeds = new ArrayList<>();

    @Getter
    @Setter
    @Builder.Default
    private int threadNum = 1;

    @Getter
    @Setter
    @Builder.Default
    private IDownloaderFactory downloaderFactory = new DefaultDownloaderFactory();

    @Getter
    @Setter
    @Builder.Default
    private List<DownloadPlugin<?>> downloadPlugins = new ArrayList<>();

    @Getter
    @Setter
    @Builder.Default
    /*
     * 异步执行
     */
    private List<Consumer> consumers = new ArrayList<>();

    @Getter
    @Setter
    @Builder.Default
    private List<Parser> parsers = new ArrayList<>();

    private final List<Thread> threads = new ArrayList<>();
    private ForkJoinPool consumerExecutor;

    @Getter
    @Setter
    private IUrlManager urlManager;

    private Context context;

    private boolean init() {
        if (context == null) {
            context = new Context();
            context.taskName(name);
        }
        if (urlManager == null) {
            urlManager = new DefaultUrlManager();
        }
        if (seeds == null) {
            log.warn("没有种子");
        } else {
            urlManager.addTargetsToFirst(context, seeds);
        }
        if (consumers.size() > 0) {
            consumerExecutor = new ForkJoinPool();
        }
        return true;
    }

    /**
     * 将等待任务完成
     */
    public void run() {
        runAsync();
        threads.forEach(t -> {
            try {
                if (t.isAlive()) {
                    t.join();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 等待异步线程
        if (consumerExecutor != null && !consumerExecutor.isShutdown()) {
            log.info("任务完成，等待异步consumer线程池结束");
            consumerExecutor.shutdown();// 等待线程池里任务执行完成后关闭
            try {
                boolean b = consumerExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
                log.info("线程池正常结束");
            } catch (InterruptedException e) {
                log.error("线程池异常结束", e);
            }
        }
    }

    /**
     * 异步执行任务
     */
    public void runAsync() {
        if (!init()) {
            return;
        }
        for (int i = 0; i < threadNum; i++) {
            Thread thread = new Thread(this::crawl);
            threads.add(thread);
            thread.start();
        }
    }

    private void crawl() {
        // 启动浏览器
        IDownloader downloader = downloaderFactory.get();
        while (true) {
            Context context = new Context()
                    .taskName(name)
                    .startTime(LocalDateTime.now())
                    .result(new JSONObject());
            String nextUrl = urlManager.next(context);
            if (nextUrl == null) {
                log.info("没有要抓取的URL，任务完成");
                break;
            }
            context.targetUrl(nextUrl);
            try {
                // 加载网页前
                boolean continueGet = true;
                for (DownloadPlugin downloadPlugin : downloadPlugins) {
                    if (!downloadPlugin.beforeDownload(downloader, context)) {
                        continueGet = false;
                        break;
                    }
                }
                if (continueGet) {
                    downloader.get(nextUrl);
                }
                // 加入已抓取队列
                urlManager.addCrawled(context, nextUrl);
                // 加载网页完成后
                continueWhile:
                for (DownloadPlugin downloadPlugin : downloadPlugins) {
                    if (!downloadPlugin.afterDownload(downloader, context)) {
                        log.info("返回FALSE，中断执行.{}", downloadPlugin.getClass().getName());
                        continue continueWhile;
                    }
                }
                context.currentUrl(downloader.getCurrentUrl()).pageSource(downloader.getPageSource())
                        .document(Jsoup.parse(context.pageSource()));
                // 解析内容
                continueWhile:
                for (Parser parser : parsers) {
                    if (!parser.parse(context)) {
                        continue continueWhile;
                    }
                }
            } catch (Exception e) {
                taskLogger.error("[{}]抓取出错。url:{}", name, nextUrl, e);
                continue;
            }
            // 添加target url
            if (CollectionUtil.isNotEmpty(context.newTargetUrls())) {
                urlManager.addTarget(context, context.newTargetUrls());
            }
            consumers.forEach(c -> consumerExecutor.execute(() -> c.consumer(context.result())));
        }
        // 关闭浏览器
        try {
            downloader.quit();
        } catch (Exception e) {
            taskLogger.error("[{}]关闭浏览器异常", name, e);
        }
    }

    public Task addDownloadPlugin(DownloadPlugin<?> i) {
        this.downloadPlugins.add(i);
        return this;
    }

    public Task addParser(Parser parser) {
        this.parsers.add(parser);
        return this;
    }

    public Task addConsumer(Consumer consumer) {
        this.consumers.add(consumer);
        return this;
    }

    /**
     * 结束任务
     */
    public void forceEnd() {
        for (Thread thread : threads) {
            thread.interrupt();
        }
    }

    public Task seeds(String... seeds) {
        return seeds(Arrays.asList(seeds));
    }

    public Task seeds(List<String> seeds) {
        this.seeds.addAll(seeds);
        return this;
    }

}
